package BLC

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"github.com/boltdb/bolt"
	"log"
)

const utxoTableName = "utxoTableName"

type UTXOSet struct {
	Blockchain *Blockchain
}

// 重置数据库表
func (utxoSet *UTXOSet) ResetUTXOSet() {

	err := utxoSet.Blockchain.DB.Update(func(tx *bolt.Tx) error {

		b := tx.Bucket([]byte(utxoTableName))

		if b != nil {

			err := tx.DeleteBucket([]byte(utxoTableName))

			if err != nil {
				log.Panic(err)
			}

		}

		b, _ = tx.CreateBucket([]byte(utxoTableName))
		if b != nil {

			//[string]*TXOutputs
			txOutputsMap := utxoSet.Blockchain.FindUTXOMap()

			for keyHash, outs := range txOutputsMap {

				txHash, _ := hex.DecodeString(keyHash)

				b.Put(txHash, outs.Serialize())

			}
		}

		return nil
	})

	if err != nil {
		log.Panic(err)
	}

}

func (utxoSet *UTXOSet) findUTXOForAddress(address string) []*UTXO {

	var utxos []*UTXO

	utxoSet.Blockchain.DB.View(func(tx *bolt.Tx) error {

		b := tx.Bucket([]byte(utxoTableName))

		// 游标
		c := b.Cursor()

		for k, v := c.First(); k != nil; k, v = c.Next() {

			txOutputs := DeserializeTXOutputs(v)

			for _, utxo := range txOutputs.UTXOS {

				if utxo.Output.UnLockScriptPubKeyWithAddress(address) {
					utxos = append(utxos, utxo)
				}
			}
		}

		return nil
	})

	return utxos
}

func (utxoSet *UTXOSet) GetBalance(address string) int64 {

	UTXOS := utxoSet.findUTXOForAddress(address)

	var amount int64

	for _, utxo := range UTXOS {
		amount += utxo.Output.Value
	}

	return amount
}

func (utxoSet *UTXOSet) FindUnpackageSpentableUTXOS(from string, txs []*Transaction) []*UTXO {

	var unUTXOs []*UTXO

	spentTXOutputs := make(map[string][]int)

	for _, tx := range txs {
		if tx.IsCoinbaseTransaction() == false {
			for _, in := range tx.Vins {

				publickKeyHash := Base58Decode([]byte(from))

				ripemd160Hash := publickKeyHash[1 : len(publickKeyHash)-4]

				if in.UnLockRipemd160Hash(ripemd160Hash) {
					key := hex.EncodeToString(in.TxHash)

					spentTXOutputs[key] = append(spentTXOutputs[key], in.Vout)
				}
			}
		}
	}

	for _, tx := range txs {

	Work1:
		for index, out := range tx.Vouts {
			if out.UnLockScriptPubKeyWithAddress(from) {

				if len(spentTXOutputs) == 0 {
					utxo := &UTXO{tx.TxHash, index, out}
					unUTXOs = append(unUTXOs, utxo)
				} else {
					for hash, indexArray := range spentTXOutputs {
						txHashStr := hex.EncodeToString(tx.TxHash)

						if hash == txHashStr {

							var isUnSpentUTXO bool

							for _, outIndex := range indexArray {
								if index == outIndex {
									isUnSpentUTXO = true
									continue Work1
								}

								if isUnSpentUTXO == false {
									utxo := &UTXO{tx.TxHash, index, out}
									unUTXOs = append(unUTXOs, utxo)
								}
							}

						} else {
							utxo := &UTXO{tx.TxHash, index, out}
							unUTXOs = append(unUTXOs, utxo)
						}
					}
				}
			}
		}
	}

	return unUTXOs

}

func (utxoSet *UTXOSet) FindSpendableUTXOS(from string, amount int64, txs []*Transaction) (int64, map[string][]int) {

	unPackageUTXOS := utxoSet.FindUnpackageSpentableUTXOS(from, txs)

	spentableUTXO := make(map[string][]int)

	var money int64 = 0

	for _, UTXO := range unPackageUTXOS {

		money += UTXO.Output.Value
		txHash := hex.EncodeToString(UTXO.TxHash)
		spentableUTXO[txHash] = append(spentableUTXO[txHash], UTXO.Index)
		if money >= amount {
			return money, spentableUTXO
		}
	}

	// 钱不够
	utxoSet.Blockchain.DB.View(func(tx *bolt.Tx) error {

		b := tx.Bucket([]byte(utxoTableName))

		if b != nil {
			c := b.Cursor()
		UTXOBREAK:
			for k, v := c.First(); k != nil; k, v = c.Next() {

				txOutputs := DeserializeTXOutputs(v)

				for _, utxo := range txOutputs.UTXOS {

					money += utxo.Output.Value
					txHash := hex.EncodeToString(utxo.TxHash)
					spentableUTXO[txHash] = append(spentableUTXO[txHash], utxo.Index)
					if money >= amount {
						break UTXOBREAK;
					}
				}

			}
		}

		return nil
	})

	if money < amount {
		log.Panic("余额不足")

	}
	return money, spentableUTXO

}

func (utxoSet *UTXOSet) Update() {

	block := utxoSet.Blockchain.Iterator().Next()

	ins := []*TXInput{}

	outsMap := make(map[string]*TXOutputs)

	// 找到所有删除的数据
	for _, tx := range block.Txs {

		for _, in := range tx.Vins {
			ins = append(ins, in)
		}
	}

	for _, tx := range block.Txs {

		utxos := []*UTXO{}

		for index, out := range tx.Vouts {

			isSpent := false

			for _, in := range ins {
				if in.Vout == index && bytes.Compare(tx.TxHash, in.TxHash) == 0 && bytes.Compare(out.Ripemd160Hash, Ripemd160Hash(in.PublicKey)) == 0 {

					isSpent = true
					continue
				}
			}

			if isSpent == false {
				utxo := &UTXO{tx.TxHash, index, out}
				utxos = append(utxos, utxo)
			}

		}

		if len(utxos) > 0 {
			txHash := hex.EncodeToString(tx.TxHash)
			outsMap[txHash] = &TXOutputs{utxos}
		}
	}

	// 更新
	err := utxoSet.Blockchain.DB.Update(func(tx *bolt.Tx) error {

		b := tx.Bucket([]byte(utxoTableName))

		if b != nil {

			// 删除
			for _, in := range ins {

				txOutputsBytes := b.Get(in.TxHash)

				if len(txOutputsBytes) == 0 {
					continue
				}

				txOutpus := DeserializeTXOutputs(txOutputsBytes)

				fmt.Println("txoutpus->", txOutpus)

				UTXOS := []*UTXO{}

				// 判断是否需要
				isNeedDelete := false

				for _,utxo := range txOutpus.UTXOS {

					if in.Vout == utxo.Index && bytes.Compare(utxo.Output.Ripemd160Hash,Ripemd160Hash(in.PublicKey)) == 0 {

						isNeedDelete  = true
					}else {
						UTXOS = append(UTXOS,utxo)
					}
				}

				if isNeedDelete {
					b.Delete(in.TxHash)
					if len(UTXOS) > 0 {
						preTXOutputs := outsMap[hex.EncodeToString(in.TxHash)]

						preTXOutputs.UTXOS = append(preTXOutputs.UTXOS,UTXOS...)

						outsMap[hex.EncodeToString(in.TxHash)] = preTXOutputs
					}
				}
			}

			// 新增
			for keyHash ,outPuts := range outsMap{
				keyHashBytes,_:= hex.DecodeString(keyHash)
				b.Put(keyHashBytes,outPuts.Serialize())
			}
		}

		return nil
	})

	if err != nil {
		log.Panic(err)
	}

}
